// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package runtime

import (
	"internal/goarch"
	"runtime/internal/atomic"
	"runtime/internal/sys"
	"unsafe"
)

// 帧可用于获取调用方返回的PC值的
// 片的函数/文件/行信息。
type Frames struct {
	// 呼叫者是尚未扩展到机架的PC的一部分。
	callers []uintptr

	// 帧是尚未返回的帧片段。
	frames     []Frame
	frameStore [2]Frame
}

// Frame是帧为每个调用帧返回的信息。
type Frame struct {
	// PC是此帧中位置的程序计数器。
	// 对于调用另一个帧的帧，这将是调用指令的
	// 程序计数器。由于内联，多个帧可能具有相同的PC值，但不同的符号信息。
	PC uintptr

	// Func是此调用帧的Func值。对于非Go代码或完全内联函数，这可能是nil 
	// 。
	Func *Func

	// 函数是此调用帧的包路径限定函数名。如果非空，则该字符串唯一地
	// 标识程序中的单个函数。
	// 如果未知，则可能是空字符串。
	// 如果Func不是nil，那么函数==Func。名称（）。
	Function string

	// 文件和行是此帧中
	// 位置的文件名和行号。对于非叶帧，这将是
	// 调用的位置。如果不知道，它们可能分别是空字符串和
	// 0。
	File string
	Line int

	// 函数的入口点程序计数器；可能是零
	// 如果不知道。如果Func不是nil，则Entry==
	// Func。Entry（）。
	Entry uintptr

	// 函数的运行时内部视图。此字段
	// 仅为Go函数设置（funcInfo.valid（）返回true），
	// 不为C函数设置。
	funcInfo funcInfo
}

// 调用者帧获取调用者返回的PC值的一部分，
// 准备返回函数/文件/行信息。
// 在完成帧之前不要更改切片。
func CallersFrames(callers []uintptr) *Frames {
	f := &Frames{callers: callers}
	f.frames = f.frameStore[:0]
	return f
}

// Next返回一个帧，表示PC值的切片
// 中的下一个调用帧。如果已经返回了所有的调用帧，则Next 
// 返回一个零帧。
// 
// 更多结果表明下一次调用next是否将返回有效帧。它不一定表示这个调用
// 是否返回了一个。
// 
// 有关惯用用法，请参见框架示例。
func (ci *Frames) Next() (frame Frame, more bool) {
	for len(ci.frames) < 2 {
		// 找到下一帧。
		// 我们需要寻找2帧，这样我们就知道返回“更多”结果的是什么。
		if len(ci.callers) == 0 {
			break
		}
		pc := ci.callers[0]
		ci.callers = ci.callers[1:]
		funcInfo := findfunc(pc)
		if !funcInfo.valid() {
			if cgoSymbolizer != nil {
				// 预扩展cgo帧。我们也可以以增量的方式进行，但无论如何，在这种情况下，我们都无法避免分配。
				ci.frames = append(ci.frames, expandCgoFrames(pc)...)
			}
			continue
		}
		f := funcInfo._Func()
		entry := f.Entry()
		if pc > entry {
			// 我们在有问题的指令（调用或内联标记）之后存储指令开始的pc。
			// 这样做是出于历史原因，是为了使FuncForPC 
			// 对运行时结果中的条目正确工作。来电者。
			pc--
		}
		name := funcname(funcInfo)
		if inldata := funcdata(funcInfo, _FUNCDATA_InlTree); inldata != nil {
			inltree := (*[1 << 20]inlinedCall)(inldata)
			// 不严格，因为cgoTraceback可能添加了伪PC 
			// 带有有效的funcInfo但无效的PCDATA。
			ix := pcdatavalue1(funcInfo, _PCDATA_InlTreeIndex, pc, nil, false)
			if ix >= 0 {
				// 注意：条目未修改。它总是指一个真实的框架，而不是一个内联的框架。
				f = nil
				name = funcnameFromNameoff(funcInfo, inltree[ix].func_)
				// 文件/行已经正确。
				// TODO:从InlinedCall中删除文件/行？
			}
		}
		ci.frames = append(ci.frames, Frame{
			PC:       pc,
			Func:     f,
			Function: name,
			Entry:    entry,
			funcInfo: funcInfo,
			// 注意：文件，行设置在
		})
	}

	// 从帧列表中弹出一帧。剩下的留着。
	// 避免在通常情况下分配，即1或2帧。
	switch len(ci.frames) {
	case 0: // 在极少数情况下，当根本没有帧时，我们返回帧{}。
		return
	case 1:
		frame = ci.frames[0]
		ci.frames = ci.frameStore[:0]
	case 2:
		frame = ci.frames[0]
		ci.frameStore[0] = ci.frames[1]
		ci.frames = ci.frameStore[:1]
	default:
		frame = ci.frames[0]
		ci.frames = ci.frames[1:]
	}
	more = len(ci.frames) > 0
	if frame.funcInfo.valid() {
		// Compute file/line就在我们需要返回它之前，因为它可能很昂贵。这避免了为我们找到的帧计算文件/行
		// 但不返回。见第32093期。
		file, line := funcline1(frame.funcInfo, frame.PC, false)
		frame.File, frame.Line = file, int(line)
	}
	return
}

// runtime_expandFinalInlineFrame将stk中的最终pc扩展为包含所有
// “调用者”（如果pc是内联的）。
// 
// go:linkname runtime_expandfinalinframe runtime/pprof。runtime_expandFinalInlineFrame 
func runtime_expandFinalInlineFrame(stk []uintptr) []uintptr {
	if len(stk) == 0 {
		return stk
	}
	pc := stk[len(stk)-1]
	tracepc := pc - 1

	f := findfunc(tracepc)
	if !f.valid() {
		// 不是Go函数。
		return stk
	}

	inldata := funcdata(f, _FUNCDATA_InlTree)
	if inldata == nil {
		// f中没有内联内容。
		return stk
	}

	// 将上一个函数视为正常。我们实际上还没有检查过，但是由于这台电脑包含在堆栈中，我们知道它不应该被删除。
	lastFuncID := funcID_normal

	// 从stk中删除pc；我们将在下面重新添加它。
	stk = stk[:len(stk)-1]

	// 参见gentraceback中的内联扩展。
	var cache pcvalueCache
	inltree := (*[1 << 20]inlinedCall)(inldata)
	for {
		// 不严格，因为cgoTraceback可能添加了伪造的PC 
		// 带有有效的funcInfo但无效的PCDATA。
		ix := pcdatavalue1(f, _PCDATA_InlTreeIndex, tracepc, &cache, false)
		if ix < 0 {
			break
		}
		if inltree[ix].funcID == funcID_wrapper && elideWrapperCalling(lastFuncID) {
			// 忽略包装
		} else {
			stk = append(stk, pc)
		}
		lastFuncID = inltree[ix].funcID
		// 备份到“调用者”中的指令。
		tracepc = f.entry() + uintptr(inltree[ix].parentPc)
		pc = tracepc + 1
	}

	// N.B.我们想保留最后一个不在线的parentPC。
	stk = append(stk, pc)

	return stk
}

// expandCgoFrames使用cgoSymbolizer钩子为pc扩展帧信息，称为
// 一个非Go函数。expandCgoFrames 
// 如果pc无法扩展，则返回nil。
func expandCgoFrames(pc uintptr) []Frame {
	arg := cgoSymbolizerArg{pc: pc}
	callCgoSymbolizer(&arg)

	if arg.file == nil && arg.funcName == nil {
		// 没有来自Symboler的有用信息。
		return nil
	}

	var frames []Frame
	for {
		frames = append(frames, Frame{
			PC:       pc,
			Func:     nil,
			Function: gostring(arg.funcName),
			File:     gostring(arg.file),
			Line:     int(arg.lineno),
			Entry:    arg.entry,
			// funcInfo为零，这意味着！funcInfo。valid（）。
			// 这确保了我们使用这里给出的文件/行信息。
		})
		if arg.more == 0 {
			break
		}
		callCgoSymbolizer(&arg)
	}

	// 这台电脑没有更多的框架。告诉符号器我们完成了。
	// 我们不尝试为
	// 框架的整个使用维护一个cgoSymbolizerArg，因为当我们完成时，没有好的方法告诉
	// 符号器。
	arg.pc = 0
	callCgoSymbolizer(&arg)

	return frames
}

// 注意：Func不公开实际未报告的字段，因为我们将*Func 
// 值返回给用户，并且我们希望防止他们能够用（比如）*f=Func{}覆盖数据
// 。
// 所有在*Func上操作的代码都必须调用raw（）来获取*Func 
// 或funcInfo（）来获取funcInfo。

// Func表示运行二进制文件中的Go函数。
type Func struct {
	opaque struct{} // 禁止转换的未报告字段
}

func (f *Func) raw() *_func {
	return (*_func)(unsafe.Pointer(f))
}

func (f *Func) funcInfo() funcInfo {
	return f.raw().funcInfo()
}

func (f *_func) funcInfo() funcInfo {
	// 查找包含fn的模块。fn位于PCLN表中。
	// 不安全。指向uintptr转换和算术
	// 的指针是安全的，因为我们使用的是模块地址。
	ptr := uintptr(unsafe.Pointer(f))
	var mod *moduledata
	for datap := &firstmoduledata; datap != nil; datap = datap.next {
		if len(datap.pclntable) == 0 {
			continue
		}
		base := uintptr(unsafe.Pointer(&datap.pclntable[0]))
		if base <= ptr && ptr < base+uintptr(len(datap.pclntable)) {
			mod = datap
			break
		}
	}
	return funcInfo{f, mod}
}

// PCDATA和FUNCDATA表索引。
// 
// 参见数据。h和/cmd/internal/objabi/funcdata。去
const (
	_PCDATA_UnsafePoint   = 0
	_PCDATA_StackMapIndex = 1
	_PCDATA_InlTreeIndex  = 2
	_PCDATA_ArgLiveIndex  = 3

	_FUNCDATA_ArgsPointerMaps    = 0
	_FUNCDATA_LocalsPointerMaps  = 1
	_FUNCDATA_StackObjects       = 2
	_FUNCDATA_InlTree            = 3
	_FUNCDATA_OpenCodedDeferInfo = 4
	_FUNCDATA_ArgInfo            = 5
	_FUNCDATA_ArgLiveInfo        = 6
	_FUNCDATA_WrapInfo           = 7

	_ArgsSizeUnknown = -0x80000000
)

const (
	// PCDATA_非安全点值。
	_PCDATA_UnsafePointSafe   = -1 // 异步抢占安全
	_PCDATA_UnsafePointUnsafe = -2 // 异步抢占不安全

	// /_PCDATA_Restart1（2）应用于
	// 中的一系列指令，如果发生异步抢占，我们应该在恢复时将PC 
	// 退回到序列的开始。
	// 我们需要两个，这样我们就可以区分序列的开始/结束，以防两个序列相邻。
	_PCDATA_Restart1 = -3
	_PCDATA_Restart2 = -4

	// 类似于_PCDATA_restartEntry，但如果异步
	// 被抢占，则返回函数项。
	_PCDATA_RestartAtEntry = -5
)

// FuncID标识需要运行时特别处理的特定函数
// 。
// 注意，在某些涉及插件的情况下，可能会有多个特定运行时函数的副本。
// 注意：此列表必须与cmd/internal/objabi/funcid中的列表匹配。去
type funcID uint8

const (
	funcID_normal funcID = iota // 不是特殊函数
	funcID_abort
	funcID_asmcgocall
	funcID_asyncPreempt
	funcID_cgocallback
	funcID_debugCallV2
	funcID_gcBgMarkWorker
	funcID_goexit
	funcID_gogo
	funcID_gopanic
	funcID_handleAsyncEvent
	funcID_mcall
	funcID_morestack
	funcID_mstart
	funcID_panicwrap
	funcID_rt0_go
	funcID_runfinq
	funcID_runtime_main
	funcID_sigpanic
	funcID_systemstack
	funcID_systemstack_switch
	funcID_wrapper // 任何自动生成的代码（哈希/均衡算法、方法包装等）
)

// FuncFlag保存函数的位。
// 此列表必须与cmd/internal/objabi/funcid中的列表匹配。去
type funcFlag uint8

const (
	// TOPFRAME表示出现在堆栈顶部的函数。ABCFDG 
	funcFlag_TOPFRAME funcFlag = 1 << iota

	// SPWRITE表示将任意值写入SP的函数
	// （除了添加或减去常量之外的任何写入）。
	// 回溯例程无法将此类更改编码到
	// pcsp表中，因此函数回溯无法安全地通过
	// SPWRITE函数展开。在SPWRITE函数处停止被视为
	// 堆栈未完全展开。在某些情况下，这是一个致命错误。
	funcFlag_SPWRITE

	// ASM表示函数已在程序集中实现。
	funcFlag_ASM
)

// pcHeader保存pclntab查找使用的数据。
type pcHeader struct {
	magic          uint32  // 0xFFFFFFF0 
	pad1, pad2     uint8   // 0,0 
	minLC          uint8   // 最小指令大小
	ptrSize        uint8   // ptr的大小（字节）
	nfunc          int     // 模块中的函数数
	nfiles         uint    // 文件选项卡中的条目数
	textStart      uintptr // 此模块中函数条目PC偏移量的基数，等于moduledata。text 
	funcnameOffset uintptr // 从pcHeader 
	cuOffset       uintptr // 从pcHeader 
	filetabOffset  uintptr // 从pcHeader 
	pctabOffset    uintptr // 从pcHeader 
	pclnOffset     uintptr // 从pcHeader 
}

// 模块数据记录有关可执行
// 图像布局的信息。它是由链接器编写的。此处的任何更改必须是
// 与cmd/link/internal/ld/symtab中的代码更改相匹配。开始：symtab。
// moduledata存储在静态分配的非指针内存中；
// 这里的指针对垃圾收集器都不可见。快走。func.*
type moduledata struct {
	pcHeader     *pcHeader
	funcnametab  []byte
	cutab        []uint32
	filetab      []byte
	pctab        []byte
	pclntable    []byte
	ftab         []functab
	findfunctab  uintptr
	minpc, maxpc uintptr

	text, etext           uintptr
	noptrdata, enoptrdata uintptr
	data, edata           uintptr
	bss, ebss             uintptr
	noptrbss, enoptrbss   uintptr
	end, gcdata, gcbss    uintptr
	types, etypes         uintptr
	rodata                uintptr

	textsectmap []textsect
	typelinks   []int32 // 与类型的偏移量
	itablinks   []*itab

	ptab []ptabEntry

	pluginpath string
	pkghashes  []modulehash

	modulename   string
	modulehashes []modulehash

	hasmain uint8 // 1如果模块包含主函数，则为0否则

	gcdatamask, gcbssmask bitvector

	typemap map[typeOff]*_type // 偏移到以前模块中的*_rtype

	bad bool // 未能加载模块，应忽略

	next *moduledata
}

// 使用modulehash将新模块或
// 新模块中的包的ABI与加载的程序进行比较。
// 
// 对于模块链接所针对的每个共享库，链接器在
// 模块数据中创建一个条目。ModuleHash切片包含模块名称、链接时看到的abi哈希
// 以及指向运行时abi哈希的指针。这些在下面的
// ModuleDatVerify中检查。
// 
// 对于每个加载的插件，pkghashes切片都有一个模块化的
// 新加载的包，可以用来检查插件版本的
// 包与之前加载的包的任何版本。
// 这是在插件中完成的。lastmoduleinit。
type modulehash struct {
	modulename   string
	linktimehash string
	runtimehash  *string
}

// pinnedTypemaps是moduledata对象的map[typeOff]*_类型。
// 
// 这些类型映射对象是在运行时在堆上分配的，但是
// 对它们的唯一直接引用是在moduledata中，该moduledata由
// 链接器创建，并标记为SNOPTRDATA，因此被GC忽略。
// 
// 为了确保地图没有被收集，我们在这里保留了第二个参考。
var pinnedTypemaps []map[typeOff]*_type

var firstmoduledata moduledata  // 链接器符号
var lastmoduledatap *moduledata // 链接器符号
var modulesSlice *[]*moduledata // 请参阅活动模块

// 活动模块返回活动模块的一部分。
// 
// 一个模块在其gcdatamask和gcbssmask组装完毕且可供GC使用后即处于活动状态。
// 
// 这是nosplit/nowritebarrier，因为它是由
// cgo指针检查代码调用的。
// go:nosplit 
// go:nowritebarrier 
func activeModules() []*moduledata {
	p := (*[]*moduledata)(atomic.Loadp(unsafe.Pointer(&modulesSlice)))
	if p == nil {
		return nil
	}
	return *p
}

// modulesinit从所有加载的模块中创建活动模块切片。
// 
// 当动态链接器首次加载模块时。调用init_数组
// 函数（由cmd/link编写）调用addmoduledata，
// 将模块附加到以
// firstmoduledata开头的链表中。
// 
// 在Go 
// 程序的生命周期中，这种情况可能发生两次。首先，如果使用-linkshared编译，可以在程序初始化时加载许多模块
// 使用-buildmode=shared编译。
// 第二，Go程序可以在运行由-buildmode=plugin构建的模块时加载模块。
// 
// 加载后，调用此函数初始化
// moduledata，使其可供GC使用，并创建一个新的activeModules 
// 列表。
// 
// 一次只能有一个goroutine调用modulesinit。
func modulesinit() {
	modules := new([]*moduledata)
	for md := &firstmoduledata; md != nil; md = md.next {
		if md.bad {
			continue
		}
		*modules = append(*modules, md)
		if md.gcdatamask == (bitvector{}) {
			scanDataSize := md.edata - md.data
			md.gcdatamask = progToPointerMask((*byte)(unsafe.Pointer(md.gcdata)), scanDataSize)
			scanBSSSize := md.ebss - md.bss
			md.gcbssmask = progToPointerMask((*byte)(unsafe.Pointer(md.gcbss)), scanBSSSize)
			gcController.addGlobals(int64(scanDataSize + scanBSSSize))
		}
	}

	// 模块按动态加载程序加载的顺序出现在moduledata链表中，但有一个例外：
	// firstmoduledata本身包含运行时的模块。这个
	// 并不总是第一个模块（当使用-buildmode=shared时，它通常是libstd.so，第二个模块）。顺序对
	// typelinksinit很重要，所以我们用
	// 包含主函数的任何模块交换第一个模块。
	// 
	// 见第18729期。
	for i, md := range *modules {
		if md.hasmain != 0 {
			(*modules)[0] = md
			(*modules)[i] = &firstmoduledata
			break
		}
	}

	atomicstorep(unsafe.Pointer(&modulesSlice), unsafe.Pointer(modules))
}

type functab struct {
	entryoff uint32 // 相对于运行时。text 
	funcoff  uint32
}

// 辅助文本节的映射信息

type textsect struct {
	vaddr    uintptr // 预链接节vaddr 
	end      uintptr // vaddr+节长度
	baseaddr uintptr // 重定位节地址
}

const minfunc = 16                 // 最小函数大小
const pcbucketsize = 256 * minfunc // pc->func查找表中的bucket大小

// findfunctab是这些结构的数组。
// 每个bucket代表4096字节的文本段。
// 每个子bucket代表256字节的文本段。
// 要查找给定pc的函数，请找到该pc的bucket和subbucket。将idx和subbucket值相加可获得
// 函数索引。然后从
// 索引开始扫描functab数组，以找到目标函数。
// 此表每4096字节代码使用20字节，或约0.5%的开销。
type findfuncbucket struct {
	idx        uint32
	subbuckets [16]byte
}

func moduledataverify() {
	for datap := &firstmoduledata; datap != nil; datap = datap.next {
		moduledataverify1(datap)
	}
}

const debugPcln = false

func moduledataverify1(datap *moduledata) {
	// 检查pclntab的格式是否有效。
	hdr := datap.pcHeader
	if hdr.magic != 0xfffffff0 || hdr.pad1 != 0 || hdr.pad2 != 0 ||
		hdr.minLC != sys.PCQuantum || hdr.ptrSize != goarch.PtrSize || hdr.textStart != datap.text {
		println("runtime: pcHeader: magic=", hex(hdr.magic), "pad1=", hdr.pad1, "pad2=", hdr.pad2,
			"minLC=", hdr.minLC, "ptrSize=", hdr.ptrSize, "pcHeader.textStart=", hex(hdr.textStart),
			"text=", hex(datap.text), "pluginpath=", datap.pluginpath)
		throw("invalid function symbol table")
	}

	// ftab是程序计数器的函数查找表。
	nftab := len(datap.ftab) - 1
	for i := 0; i < nftab; i++ {
		// 注：ftab[nftab]。进入是合法的；它是最终功能之外的地址。
		if datap.ftab[i].entryoff > datap.ftab[i+1].entryoff {
			f1 := funcInfo{(*_func)(unsafe.Pointer(&datap.pclntable[datap.ftab[i].funcoff])), datap}
			f2 := funcInfo{(*_func)(unsafe.Pointer(&datap.pclntable[datap.ftab[i+1].funcoff])), datap}
			f2name := "end"
			if i+1 < nftab {
				f2name = funcname(f2)
			}
			println("function symbol table not sorted by PC offset:", hex(datap.ftab[i].entryoff), funcname(f1), ">", hex(datap.ftab[i+1].entryoff), f2name, ", plugin:", datap.pluginpath)
			for j := 0; j <= i; j++ {
				println("\t", hex(datap.ftab[j].entryoff), funcname(funcInfo{(*_func)(unsafe.Pointer(&datap.pclntable[datap.ftab[j].funcoff])), datap}))
			}
			if GOOS == "aix" && isarchive {
				println("-Wl,-bnoobjreorder is mandatory on aix/ppc64 with c-archive")
			}
			throw("invalid runtime symbol table")
		}
	}

	min := datap.textAddr(datap.ftab[0].entryoff)
	max := datap.textAddr(datap.ftab[nftab].entryoff)
	if datap.minpc != min || datap.maxpc != max {
		println("minpc=", hex(datap.minpc), "min=", hex(min), "maxpc=", hex(datap.maxpc), "max=", hex(max))
		throw("minpc or maxpc invalid")
	}

	for _, modulehash := range datap.modulehashes {
		if modulehash.linktimehash != *modulehash.runtimehash {
			println("abi mismatch detected between", datap.modulename, "and", modulehash.modulename)
			throw("abi mismatch")
		}
	}
}

// textAddr返回md.text+off，对多个文本段进行特殊处理。
// off是在外部链接器调整节的基址之前，在内部链接时计算的（虚拟）偏移量，
// 。
// 
// 文本或指令流作为一个大缓冲区生成。
// 函数的off（offset）是其在该缓冲区内的偏移量。
// 如果总文本大小过大，在ppc64等平台上可能会出现问题
// 如果调用的目标距离调用指令太远。
// 为了解决大文本问题，文本被拆分为多个文本部分
// 以允许链接器在必要时生成长调用。
// 发生这种情况时，每个文本节的vaddr都设置为其在文本中的偏移量。shu jian defg
// 然后将节相对偏移量添加到节的
// 重新定位baseaddr以计算函数地址。
// 
// 它是不完整的，因为它是findfunc实现的一部分。
// go:nosplit 
func (md *moduledata) textAddr(off32 uint32) uintptr {
	off := uintptr(off32)
	res := md.text + off
	if len(md.textsectmap) > 1 {
		for i, sect := range md.textsectmap {
			// 在最后一节中，包括结束地址（etext），因为它包含在functab中。
			if off >= sect.vaddr && off < sect.end || (i == len(md.textsectmap)-1 && off == sect.end) {
				res = sect.baseaddr + off - sect.vaddr
				break
			}
		}
		if res > md.etext && GOARCH != "wasm" { // 在wasm上，函数与线性内存不在同一地址空间中
			println("runtime: textAddr", hex(res), "out of range", hex(md.text), "-", hex(md.etext))
			throw("runtime: text offset out of range")
		}
	}
	return res
}

// textOff与textAddr相反。它将PC转换为（虚拟）偏移量
// 到md.text，并返回PC是否位于任何Go text部分。
// 
// 它是不完整的，因为它是findfunc实现的一部分。
// go:nosplit 
func (md *moduledata) textOff(pc uintptr) (uint32, bool) {
	res := uint32(pc - md.text)
	if len(md.textsectmap) > 1 {
		for i, sect := range md.textsectmap {
			if sect.baseaddr > pc {
				// pc不在任何部分中。
				return 0, false
			}
			end := sect.baseaddr + (sect.end - sect.vaddr)
			// 在最后一节中，包括结束地址（etext），因为它包含在functab中。
			if i == len(md.textsectmap) {
				end++
			}
			if pc < end {
				res = uint32(pc - sect.baseaddr + sect.vaddr)
				break
			}
		}
	}
	return res, true
}

// FuncForPC返回一个*Func，描述包含给定程序计数器地址的函数，否则为零。
// 
// 如果pc由于内联而代表多个函数，它将返回
// 描述最内部函数的*函数，但最外部函数的条目为
// 。
func FuncForPC(pc uintptr) *Func {
	f := findfunc(pc)
	if !f.valid() {
		return nil
	}
	if inldata := funcdata(f, _FUNCDATA_InlTree); inldata != nil {
		// 注意：strict=false，这样坏的PC（函数之间的PC）不会使运行时崩溃。
		// 在这种情况下，我们只报告前面的函数。见第29735期。
		// TODO:在这种情况下，也许我们不应该报告任何函数。
		// 运行时当前没有函数结束信息，唉。
		if ix := pcdatavalue1(f, _PCDATA_InlTreeIndex, pc, nil, false); ix >= 0 {
			inltree := (*[1 << 20]inlinedCall)(inldata)
			name := funcnameFromNameoff(f, inltree[ix].func_)
			file, line := funcline(f, pc)
			fi := &funcinl{
				ones:  ^uint32(0),
				entry: f.entry(), // 实函数（最外层）的输入。
				name:  name,
				file:  file,
				line:  int(line),
			}
			return (*Func)(unsafe.Pointer(fi))
		}
	}
	return f._Func()
}

// Name返回函数的名称。
func (f *Func) Name() string {
	if f == nil {
		return ""
	}
	fn := f.raw()
	if fn.isInlined() { // 内联版本
		fi := (*funcinl)(unsafe.Pointer(fn))
		return fi.name
	}
	return funcname(f.funcInfo())
}

// Entry返回函数的入口地址。
func (f *Func) Entry() uintptr {
	fn := f.raw()
	if fn.isInlined() { // 内联版本
		fi := (*funcinl)(unsafe.Pointer(fn))
		return fi.entry
	}
	return fn.funcInfo().entry()
}

// FileLine返回程序计数器pc对应的
// 源代码的文件名和行号。
// 如果pc不是程序
// f内的计数器。
func (f *Func) FileLine(pc uintptr) (file string, line int) {
	fn := f.raw()
	if fn.isInlined() { // 内联版本
		fi := (*funcinl)(unsafe.Pointer(fn))
		return fi.file, fi.line
	}
	// 此处Pass strict=false，因为任何人都可以调用这个函数，所以对于属于f的targetpc，他们可能是错的。findmoduledatap查找PC的moduledata。因为它是isgoexception的一部分，所以它是不完整的。
	file, line32 := funcline1(f.funcInfo(), pc, false)
	return file, int(line32)
}

// 
// go:nosplit 
func findmoduledatap(pc uintptr) *moduledata {
	for datap := &firstmoduledata; datap != nil; datap = datap.next {
		if datap.minpc <= pc && pc < datap.maxpc {
			return datap
		}
	}
	return nil
}

type funcInfo struct {
	*_func
	datap *moduledata
}

func (f funcInfo) valid() bool {
	return f._func != nil
}

func (f funcInfo) _Func() *Func {
	return (*Func)(unsafe.Pointer(f._func))
}

// 是关于f是否应重新解释为*函数的在线报告。
func (f *_func) isInlined() bool {
	return f.entryoff == ^uint32(0) // 有关函数，请参见注释。ones 
}

// entry返回f的条目PC。
func (f funcInfo) entry() uintptr {
	return f.datap.textAddr(f.entryoff)
}

// findfunc查找PC的函数元数据。
// 
// 它是不完整的，因为它是isgoexception 
// 实现的一部分。
// 
// go:nosplit 
func findfunc(pc uintptr) funcInfo {
	datap := findmoduledatap(pc)
	if datap == nil {
		return funcInfo{}
	}
	const nsub = uintptr(len(findfuncbucket{}.subbuckets))

	pcOff, ok := datap.textOff(pc)
	if !ok {
		return funcInfo{}
	}

	x := uintptr(pcOff) + datap.text - datap.minpc // TODO:are datap。文本和数据处理。minpc总是平等的吗？
	b := x / pcbucketsize
	i := x % pcbucketsize / (pcbucketsize / nsub)

	ffb := (*findfuncbucket)(add(unsafe.Pointer(datap.findfunctab), b*unsafe.Sizeof(findfuncbucket{})))
	idx := ffb.idx + uint32(ffb.subbuckets[i])

	// 查找ftab条目。
	for datap.ftab[idx+1].entryoff <= pcOff {
		idx++
	}

	funcoff := datap.ftab[idx].funcoff
	return funcInfo{(*_func)(unsafe.Pointer(&datap.pclntable[funcoff])), datap}
}

type pcvalueCache struct {
	entries [2][8]pcvalueCacheEnt
}

type pcvalueCacheEnt struct {
	// targetpc和off一起是该缓存项的键。
	targetpc uintptr
	off      uint32
	// val是此缓存pcvalue项的值。
	val int32
}

// pcvalueCacheKey返回pcvalueCache中用于targetpc的最外层索引。
// 计算起来一定很便宜。
// 目前，与goarch保持一致。PtrSize并减少mod条目的数量。
// 实际上，这似乎是相当随机和均匀分布的。
func pcvalueCacheKey(targetpc uintptr) uintptr {
	return (targetpc / goarch.PtrSize) % uintptr(len(pcvalueCache{}.entries))
}

// 返回PCData值，以及该值开始的PC。
// TODO:只有当缓存为零时，才会返回启动PC。
func pcvalue(f funcInfo, off uint32, targetpc uintptr, cache *pcvalueCache, strict bool) (int32, uintptr) {
	if off == 0 {
		return -1, 0
	}

	// 检查缓存。这加快了深层堆栈的移动，而
	// 往往一次又一次地拥有相同的递归函数。
	// 
	// 此缓存足够小，完全关联性
	// 比对关联性较低的
	// 缓存进行哈希运算要便宜。
	if cache != nil {
		x := pcvalueCacheKey(targetpc)
		for i := range cache.entries[x] {
			// 我们首先检查，因为我们更
			// 可能有多个条目具有
			// 相同目标的不同偏移量，而不是相反，因此我们通常会在第一个子句中失败。
			ent := &cache.entries[x][i]
			if ent.off == off && ent.targetpc == targetpc {
				return ent.val, 0
			}
		}
	}

	if !f.valid() {
		if strict && panicking == 0 {
			println("runtime: no module data for", hex(f.entry()))
			throw("no module data")
		}
		return -1, 0
	}
	datap := f.datap
	p := datap.pctab[off:]
	pc := f.entry()
	prevpc := pc
	val := int32(-1)
	for {
		var ok bool
		p, ok = step(p, &pc, &val, pc == f.entry())
		if !ok {
			break
		}
		if targetpc < pc {
			// 替换缓存中的随机项。随机
			// 如果递归堆栈的周期略大于缓存，则替换可防止性能下降。
			// 将新元素放在开头，因为它是最有可能被新使用的元素。
			if cache != nil {
				x := pcvalueCacheKey(targetpc)
				e := &cache.entries[x]
				ci := fastrandn(uint32(len(cache.entries[x])))
				e[ci] = e[0]
				e[0] = pcvalueCacheEnt{
					targetpc: targetpc,
					off:      off,
					val:      val,
				}
			}

			return val, prevpc
		}
		prevpc = pc
	}

	// 如果有一个表，它应该包含所有程序计数器。
	// 如果没有，就有问题了。
	if panicking != 0 || !strict {
		return -1, 0
	}

	print("runtime: invalid pc-encoded table f=", funcname(f), " pc=", hex(pc), " targetpc=", hex(targetpc), " tab=", p, "\n")

	p = datap.pctab[off:]
	pc = f.entry()
	val = -1
	for {
		var ok bool
		p, ok = step(p, &pc, &val, pc == f.entry())
		if !ok {
			break
		}
		print("\tvalue=", val, " until pc=", hex(pc), "\n")
	}

	throw("invalid runtime symbol table")
	return -1, 0
}

func cfuncname(f funcInfo) *byte {
	if !f.valid() || f.nameoff == 0 {
		return nil
	}
	return &f.datap.funcnametab[f.nameoff]
}

func funcname(f funcInfo) string {
	return gostringnocopy(cfuncname(f))
}

func funcpkgpath(f funcInfo) string {
	name := funcname(f)
	i := len(name) - 1
	for ; i > 0; i-- {
		if name[i] == '/' {
			break
		}
	}
	for ; i < len(name); i++ {
		if name[i] == '.' {
			break
		}
	}
	return name[:i]
}

func cfuncnameFromNameoff(f funcInfo, nameoff int32) *byte {
	if !f.valid() {
		return nil
	}
	return &f.datap.funcnametab[nameoff]
}

func funcnameFromNameoff(f funcInfo, nameoff int32) string {
	return gostringnocopy(cfuncnameFromNameoff(f, nameoff))
}

func funcfile(f funcInfo, fileno int32) string {
	datap := f.datap
	if !f.valid() {
		return "?"
	}
	// 确保cu索引和文件偏移量有效
	if fileoff := datap.cutab[f.cuOffset+uint32(fileno)]; fileoff != ^uint32(0) {
		return gostringnocopy(&datap.filetab[fileoff])
	}
	// pcln节已损坏。
	return "?"
}

func funcline1(f funcInfo, targetpc uintptr, strict bool) (file string, line int32) {
	datap := f.datap
	if !f.valid() {
		return "?", 0
	}
	fileno, _ := pcvalue(f, f.pcfile, targetpc, nil, strict)
	line, _ = pcvalue(f, f.pcln, targetpc, nil, strict)
	if fileno == -1 || line == -1 || int(fileno) >= len(datap.filetab) {
		// print（“查找”，十六进制（targetpc），“in”，funcname（f），“get file=，fileno，“line=，lineno，”\n”）
		return "?", 0
	}
	file = funcfile(f, fileno)
	return
}

func funcline(f funcInfo, targetpc uintptr) (file string, line int32) {
	return funcline1(f, targetpc, true)
}

func funcspdelta(f funcInfo, targetpc uintptr, cache *pcvalueCache) int32 {
	x, _ := pcvalue(f, f.pcsp, targetpc, cache, true)
	if debugPcln && x&(goarch.PtrSize-1) != 0 {
		print("invalid spdelta ", funcname(f), " ", hex(f.entry()), " ", hex(targetpc), " ", hex(f.pcsp), " ", x, "\n")
		throw("bad spdelta")
	}
	return x
}

// funcMaxSPDelta返回f中任意点的最大spdelta。
func funcMaxSPDelta(f funcInfo) int32 {
	datap := f.datap
	p := datap.pctab[f.pcsp:]
	pc := f.entry()
	val := int32(-1)
	max := int32(0)
	for {
		var ok bool
		p, ok = step(p, &pc, &val, pc == f.entry())
		if !ok {
			return max
		}
		if val > max {
			max = val
		}
	}
}

func pcdatastart(f funcInfo, table uint32) uint32 {
	return *(*uint32)(add(unsafe.Pointer(&f.nfuncdata), unsafe.Sizeof(f.nfuncdata)+uintptr(table)*4))
}

func pcdatavalue(f funcInfo, table uint32, targetpc uintptr, cache *pcvalueCache) int32 {
	if table >= f.npcdata {
		return -1
	}
	r, _ := pcvalue(f, pcdatastart(f, table), targetpc, cache, true)
	return r
}

func pcdatavalue1(f funcInfo, table uint32, targetpc uintptr, cache *pcvalueCache, strict bool) int32 {
	if table >= f.npcdata {
		return -1
	}
	r, _ := pcvalue(f, pcdatastart(f, table), targetpc, cache, strict)
	return r
}

// Like pcdatavalue，但也返回该PCData值的起始PC。
// 它不需要缓存。
func pcdatavalue2(f funcInfo, table uint32, targetpc uintptr) (int32, uintptr) {
	if table >= f.npcdata {
		return -1, 0
	}
	return pcvalue(f, pcdatastart(f, table), targetpc, nil, true)
}

// funcdata返回指向f的第i个funcdata的指针。
// funcdata应与cmd/link:writeFuncs保持同步。
func funcdata(f funcInfo, i uint8) unsafe.Pointer {
	if i < 0 || i >= f.nfuncdata {
		return nil
	}
	base := f.datap.gofunc // 提前加载gofunc地址，以便我们在缓存未命中期间进行计算
	p := uintptr(unsafe.Pointer(&f.nfuncdata)) + unsafe.Sizeof(f.nfuncdata) + uintptr(f.npcdata)*4 + uintptr(i)*4
	off := *(*uint32)(unsafe.Pointer(p))
	// 返回值==^uint32（0）？0:f.datap。gofunc+uintpttr（关闭），但没有分支。
	// 编译器使用条件赋值在大多数体系结构上计算掩码。
	var mask uintptr
	if off == ^uint32(0) {
		mask = 1
	}
	mask--
	raw := base + uintptr(off)
	return unsafe.Pointer(raw & mask)
}

// 步骤前进到下一个pc，编码表中的值对。
func step(p []byte, pc *uintptr, val *int32, first bool) (newp []byte, ok bool) {
	// 对于uvdelta和pcdelta，通常情况下（~70%）
	// 是一个单字节。如果是这样，请避免致电readvarint。
	uvdelta := uint32(p[0])
	if uvdelta == 0 && !first {
		return nil, false
	}
	n := uint32(1)
	if uvdelta&0x80 != 0 {
		n, uvdelta = readvarint(p)
	}
	*val += int32(-(uvdelta & 1) ^ (uvdelta >> 1))
	p = p[n:]

	pcdelta := uint32(p[0])
	n = 1
	if pcdelta&0x80 != 0 {
		n, pcdelta = readvarint(p)
	}
	p = p[n:]
	*pc += uintptr(pcdelta * sys.PCQuantum)
	return p, true
}

// readvarint从p.
func readvarint(p []byte) (read uint32, val uint32) {
	var v, shift, n uint32
	for {
		b := p[n]
		n++
		v |= uint32(b&0x7F) << (shift & 31)
		if b&0x80 == 0 {
			break
		}
		shift += 7
	}
	return n, v
}

type stackmap struct {
	n        int32   // 位图数
	nbit     int32   // 每个位图中的位数
	bytedata [1]byte // 位图，每个位图从字节边界开始
}

// go:nowritebarrier 
func stackmapdata(stkmap *stackmap, n int32) bitvector {
	// 仅当stackDebug处于启用状态时才检查此不变量。
	// 许多stackmapdata的调用者已经检查了该不变量，
	// 默认情况下禁用该不变量可以使stackmapdata内联。
	if stackDebug > 0 && (n < 0 || n >= stkmap.n) {
		throw("stackmapdata: index out of range")
	}
	return bitvector{stkmap.nbit, addb(&stkmap.bytedata[0], uintptr(n*((stkmap.nbit+7)>>3)))}
}

// inlinedCall是FUNCDATA_InlTree表中条目的编码。
type inlinedCall struct {
	parent   int16  // 内联树中父级的索引，或<0 
	funcID   funcID // 被调用函数的类型
	_        byte
	file     int32 // 内联调用的perCU文件索引。参见cmd/link:pcln。转到
	line     int32 // 调用位置的行号
	func_    int32 // 偏移量进入被调用函数名的pclntab 
	parentPc int32 // 源位置为调用位置的指令的位置（从入口的偏移量）
}
